﻿using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Runtime.InteropServices;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ESRI.ArcGIS.Carto;
using ESRI.ArcGIS.Display;
using ESRI.ArcGIS.esriSystem;
using ESRI.ArcGIS.Geometry;
using ESRI.ArcGIS.Output;
using iTextSharp.text;
using iTextSharp.text.pdf;
using ESRI.ArcGIS.ADF.ArcGISServer;
using ESRI.ArcGIS.SOESupport;
using WorldViewSolutions.ArcGISServer.SOE.DataSources;

namespace WorldViewSolutions.ArcGISServer.SOE
{
    /// <summary>
    /// Contains information about a single MXD file who is being used as template for generating a map export.
    /// </summary>
	public class PrintTemplate
	{
        /// <summary>
        /// Gets the desktop window.
        /// </summary>
        /// <returns></returns>
		[DllImport("user32.dll")]
		public static extern IntPtr GetDesktopWindow();

        #region [ Local Variables ]
        /// <summary>
        /// 
        /// </summary>
        private ESRI.ArcGIS.esriSystem.ILog _log;
        
        /// <summary>
        /// 
        /// </summary>
        private double _mapWidthInInches = 0;

        /// <summary>
        /// 
        /// </summary>
        private double _mapHeightInInches = 0;

        #endregion

        #region [ Properties ]

        /// <summary>
        /// Gets or sets the output dir.
        /// </summary>
        /// <value>The output dir.</value>
        /// <remarks></remarks>
        public string OutputDir { get; set; }

        /// <summary>
        /// Gets the MXD.
        /// </summary>
        /// <remarks></remarks>
        public string MXD { get; private set; }

        /// <summary>
        /// Gets the ID.
        /// </summary>
        /// <remarks></remarks>
        public int ID { get; private set; }

        /// <summary>
        /// Gets the name.
        /// </summary>
        /// <remarks></remarks>
        public string Name { get; private set; }

        /// <summary>
        /// Gets the description.
        /// </summary>
        /// <remarks></remarks>
        public string Description { get; private set; }

        /// <summary>
        /// Gets the width of the page.
        /// </summary>
        /// <remarks></remarks>
        public double PageWidth { get; private set; }

        /// <summary>
        /// Gets the height of the page.
        /// </summary>
        /// <remarks></remarks>
        public double PageHeight { get; private set; }

        /// <summary>
        /// Gets the orientation.
        /// </summary>
        /// <remarks></remarks>
        public PageOrientation Orientation { get; private set; }

        /// <summary>
        /// Gets a value indicating whether this instance has thumbnail.
        /// </summary>
        /// <remarks></remarks>
        public Boolean HasThumbnail { get; private set; }

        /// <summary>
        /// Gets the thumbnail.
        /// </summary>
        /// <remarks></remarks>
        public System.Drawing.Image Thumbnail { get; private set; }

        /// <summary>
        /// Gets the width of the map.
        /// </summary>
        /// <remarks></remarks>
        public int MapWidth
        {
            get { return Convert.ToInt32(Math.Floor(_mapWidthInInches * 96)); }
        }

        /// <summary>
        /// Gets the height of the map.
        /// </summary>
        /// <remarks></remarks>
		public int MapHeight
        {
            get { return Convert.ToInt32(Math.Floor(_mapHeightInInches * 96)); }
        }

        /// <summary>
        /// Gets a value indicating whether this instance has legend.
        /// </summary>
        /// <remarks></remarks>
        public Boolean HasLegend { get; private set; }

        /// <summary>
        /// Gets a value indicating whether this instance has scalebar.
        /// </summary>
        /// <remarks></remarks>
        public Boolean HasScalebar { get; private set; }

        /// <summary>
        /// Gets a value indicating whether this instance has north arrow.
        /// </summary>
        /// <remarks></remarks>
        public Boolean HasNorthArrow { get; private set; }

        /// <summary>
        /// Gets the text elements.
        /// </summary>
        /// <remarks></remarks>
        public string[] TextElements { get; private set; }
        		
        #endregion

        /// <summary>
        /// Initializes a new instance of the <see cref="PrintTemplate"/> class.
        /// </summary>
        /// <param name="id">The id.</param>
        /// <param name="mxd">The MXD.</param>
        /// <param name="log">The log.</param>
        /// <param name="outputDir">The output dir.</param>
        /// <remarks></remarks>
        public PrintTemplate(int id, string mxd, ESRI.ArcGIS.esriSystem.ILog log, string outputDir)
		{
            this.ID = id;
			this.MXD = mxd;
            _log = log;
            OutputDir = outputDir;
            TextElements = new string[0];

            if (!File.Exists(this.MXD))
                throw new FileNotFoundException(this.MXD + " not found.");

            ParseMXDProperties();
        }


        /// <summary>
        /// Toes the json object.
        /// </summary>
        /// <returns></returns>
        /// <remarks></remarks>
        public JsonObject ToJsonObject()
        {
            JsonObject jo = new JsonObject();
            jo.AddLong("id", ID);
            jo.AddString("name", Name);
            jo.AddString("description", Description);
            jo.AddDouble("pageWidth", PageWidth);
            jo.AddDouble("pageHeight", PageHeight);
            jo.AddDouble("mapWidth", _mapWidthInInches);
            jo.AddDouble("mapHeight", _mapHeightInInches);
            jo.AddString("orientation", Orientation.ToString());
            jo.AddArray("textElements", TextElements);
            jo.AddBoolean("hasThumbnail", HasThumbnail);

            return jo;
        }

        /// <summary>
        /// Parses the MXD properties.
        /// </summary>
        /// <remarks></remarks>
        private void ParseMXDProperties()
        {
            ESRI.ArcGIS.Carto.MapReader mapReader = null;

            try
            {
                //Open the Template document
                mapReader = new MapReaderClass();
                mapReader.Open(this.MXD);

                // activate the objects
                int intHWnd = GetDesktopWindow().ToInt32();
                ((IActiveView)mapReader.PageLayout).Activate(intHWnd);
                for (int i = 0; i < mapReader.MapCount; i++)
                {
                    ((IActiveView)mapReader.get_Map(i)).Activate(intHWnd);
                }

                // Get the page layout
                IPageLayout pageLayout = mapReader.PageLayout;

                // Name
                this.Name = System.IO.Path.GetFileNameWithoutExtension(this.MXD);

                // Description
                this.Description = mapReader.DocumentInfo.GetProperty("Comments").ToString();

                // Page width and hieght
                double pageWidth, pageHeight;
                pageLayout.Page.QuerySize(out pageWidth, out pageHeight);
                this.PageWidth = pageWidth;
                this.PageHeight = pageHeight;

                // Page orientation
                this.Orientation = pageLayout.Page.Orientation == 1 ? PageOrientation.Portrait : PageOrientation.Landscape;

                // Thumbnail
                if (mapReader.Thumbnail != null)
                {
                    System.Drawing.Image thumbnail = AxHostConverter.PictureDispToImage(mapReader.Thumbnail);
                    if (thumbnail != null)
                    {
                        this.HasThumbnail = true;
                        this.Thumbnail = thumbnail;
                    }
                }

                // Layout page elements
                List<String> textElements = new List<string>();
				IGraphicsContainer graphicsContainer = pageLayout as IGraphicsContainer;
				graphicsContainer.Reset();
				ESRI.ArcGIS.Carto.IElement element = graphicsContainer.Next();

                while (element != null)
                {
                    if (element is IMapFrame)
                    {
                        // Get the widht and hieght of the map
                        IMapFrame frame = element as IMapFrame;
                        IEnvelope envelope = element.Geometry.Envelope;

                        _mapWidthInInches = Math.Round(envelope.Width, 2);
                        _mapHeightInInches = Math.Round(envelope.Height, 2);

                    }
                    else if (element is ITextElement)
                    {
                        // Check to see if this is a updateable text element (starts and ends with {})
                        ITextElement textElement = element as ITextElement;
                        if (textElement.Text.StartsWith("{") && textElement.Text.EndsWith("}"))
                        {
                            var key = textElement.Text.Substring(1, textElement.Text.Length - 2);
                            textElements.Add(key);
                        }
                    }
                    else if (element is IMapSurroundFrame)
                    {
                        IMapSurroundFrame mapSurroundFrame = element as IMapSurroundFrame;
                        IMapSurround mapSurround = mapSurroundFrame.MapSurround;

                        if (mapSurround is IMarkerNorthArrow)
                        {
                            this.HasNorthArrow = true;
                        }
                        else if (mapSurround is IScaleBar)
                        {
                            this.HasScalebar = true;
                        }
                    }
                    else if (element is ESRI.ArcGIS.Carto.RectangleElement)
                    {
                        IElementProperties elementProperties = element as IElementProperties;

                        if (elementProperties.Name.ToLower() == "{legend}")
                        {
                            this.HasLegend = true;
                        }
                    }
                    element = graphicsContainer.Next();
                }

                TextElements = textElements.ToArray();

                mapReader.Close();
            }
            finally
            {
                if (mapReader != null)
                {
                    mapReader.Close();
                }
            }
        }

        /// <summary>
        /// Creates the PDF.
        /// </summary>
        /// <param name="exportParameters">The export parameters.</param>
        /// <returns></returns>
        public string CreatePDF(ExportParameters exportParameters)
        {
            string fileName = "_ags_" + Guid.NewGuid().ToString().Replace("-", "") + ".pdf";
            string filePath = System.IO.Path.Combine(OutputDir, fileName);

            exportParameters.Width = _mapWidthInInches;
            exportParameters.Height = _mapHeightInInches;
            exportParameters.FixExent();

            Document doc = null;

            ESRI.ArcGIS.Carto.MapReader mapReader = null;
            try
            {
                //Open the Template document
                mapReader = new MapReaderClass();
                mapReader.Open(MXD);

                // activate the objects
                int intHWnd = GetDesktopWindow().ToInt32();
                ((IActiveView)mapReader.PageLayout).Activate(intHWnd);
                for (int i = 0; i < mapReader.MapCount; i++)
                {
                    ((IActiveView)mapReader.get_Map(i)).Activate(intHWnd);
                }

                IPageLayout pageLayout = mapReader.PageLayout;

                _log.AddMessage(3, 8000, "Clearing existings map layers");
                //TODO: handle more than one dataframe.
                IMap templateMap = mapReader.get_Map(0);
                ILayer templateLayer = null;
                while (templateMap.LayerCount > 0)
                {
                    templateLayer = templateMap.get_Layer(0);
                    templateMap.DeleteLayer(templateLayer);
                }
                templateMap.SpatialReference = exportParameters.COMSpatialReference;

                IRgbColor pageBackgroundColor = (IRgbColor)pageLayout.Page.BackgroundColor;
                System.Drawing.Color bgColor = System.Drawing.Color.FromArgb(pageBackgroundColor.Red, pageBackgroundColor.Green, pageBackgroundColor.Blue);
                bgColor = System.Drawing.Color.White;

                iTextSharp.text.Rectangle pageSize = new iTextSharp.text.Rectangle(Convert.ToSingle(this.PageWidth) * 72, Convert.ToSingle(this.PageHeight) * 72);
                doc = new Document(pageSize, 0.25f, 0.25f, 0.25f, 0.25f); // create an empty document
                PdfWriter writer = PdfWriter.GetInstance(doc, new System.IO.FileStream(filePath, FileMode.Create)); // tie a PdfWriter instance to the stream

                doc.Open();

                // Loop through all the elements in the pagelayout and find the map frame.
                // Once found, add all the map resources to that area
                IGraphicsContainer graphicsContainer = pageLayout as IGraphicsContainer;
                graphicsContainer.Reset();
                ESRI.ArcGIS.Carto.IElement element = graphicsContainer.Next();

                while (element != null)
                {
                    if (element is IMapFrame)
                    {
                        IMapFrame frame = element as IMapFrame;

                        ISymbolBorder border = frame.Border as ISymbolBorder;

                        IEnvelope envelope = element.Geometry.Envelope;
                        float x = Convert.ToSingle(envelope.XMin * 72) + doc.RightMargin;
                        float y = Convert.ToSingle(envelope.YMin * 72) + doc.TopMargin;


                        // add all cached layers
                        EncoderParameter qualityParam = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, 90L);
                        ImageCodecInfo jpegCodec = GetEncoderInfo("image/jpeg");
                        EncoderParameters encoderParams = new EncoderParameters(1);
                        encoderParams.Param[0] = qualityParam;

                        foreach (ExportMapResource resource in exportParameters.Resources)
                        {
                            if (resource.Resource is AgsMapResource) continue;

                            _log.AddMessage(1, 8000, "Exporting " + resource.Resource.GetType().ToString());

                            // Get the image
                            Bitmap mapImage = resource.Resource.DrawExtent(_mapWidthInInches, _mapHeightInInches, resource.DPI, resource.DPI, exportParameters.Extent);

                            // save the image to disk
                            string mapFileName = System.IO.Path.Combine(OutputDir, "_ags_" + Guid.NewGuid().ToString().Replace("-", "") + ".jpg");
                            mapImage.Save(mapFileName, jpegCodec, encoderParams);

                            // cerate an itextsharp image using the image file
                            iTextSharp.text.Image mapImg = iTextSharp.text.Image.GetInstance(mapFileName);
                            mapImg.SetAbsolutePosition(x, y);                                                      
                            mapImg.ScaleToFit(Convert.ToSingle(_mapWidthInInches * 72), Convert.ToSingle(_mapHeightInInches * 72));

                            // add the image to the pdf in it's own layer so it can be turned on/off
                            PdfLayer layer = new PdfLayer(resource.Name, writer);
                            writer.DirectContent.BeginLayer(layer);
                            writer.DirectContent.AddImage(mapImg);
                            writer.DirectContent.EndLayer();
                        }

                        // add dynamic layers. 
                        List<ExportedLayer> images = new List<ExportedLayer>();
                        foreach (ExportMapResource resource in exportParameters.Resources)
                        {
                           
                            if (resource.Resource is AgsMapResource)
                            {
                                _log.AddMessage(1, 8000, "Exporting AgsMapResource");
                                AgsMapResource agsResource = (AgsMapResource)resource.Resource;

                                // Export the resource in vector/PDF format. Each layer is exported separately so they can be added to 
                                // the PDF as separate layers that can be turned on/off independently.
                                images = agsResource.DrawExtentsToPDF(_mapWidthInInches, _mapHeightInInches, exportParameters.Extent);

                                Dictionary<int, PdfLayer> pdfLayers = new Dictionary<int, PdfLayer>();

                                // Create a pdf layer for each exported map layer
                                for (int i = 0; i < images.Count; i++)
                                {                                    
                                    ExportedLayer pdfData = images[i];
                                    pdfLayers.Add(i, new PdfLayer(pdfData.Name, writer));
                                }

                                // Create a parent resource pdflayer that will contain each child map layer.
                                PdfLayer agsLayer = new PdfLayer(resource.Name, writer);

                                writer.DirectContent.SaveState();

                                // set the fill/stroke opacity
                                PdfGState gs1 = new PdfGState();
                                gs1.FillOpacity = (float)resource.Opacity;
                                gs1.StrokeOpacity = (float)resource.Opacity;
                                writer.DirectContent.SetGState(gs1);

                                // Add the images in reverse order
                                for (int i = images.Count - 1; i >= 0; i--)
                                {
                                    ExportedLayer pdfData = images[i];
                                    PdfReader reader = new PdfReader(pdfData.Data);
                                    PdfImportedPage pg = writer.GetImportedPage(reader, 1); // import page 1, the only page	

                                    PdfLayer layer2 = pdfLayers[i];
                                    agsLayer.AddChild(layer2);
                                    writer.DirectContent.BeginLayer(layer2);
                                    writer.DirectContent.AddTemplate(pg, x, y);
                                    writer.DirectContent.EndLayer();
                                }

                                writer.DirectContent.RestoreState();
                            }
                        }

                        // Add markup
                        if (exportParameters.Graphics.Count > 0)
                        {
                            DrawMarkup(writer, x, y, exportParameters);
                        }

                        // Add the border
                        if (border.LineSymbol.Width > 0)
                        {
                            writer.DirectContent.SetLineWidth(Convert.ToSingle(border.LineSymbol.Width));
                            writer.DirectContent.Rectangle(x, y, Convert.ToSingle(_mapWidthInInches * 72), Convert.ToSingle(_mapHeightInInches * 72));
                            writer.DirectContent.Stroke();
                        }


                        break;
                    }

                    element = graphicsContainer.Next();
                }

                /* 
                 * Prepare the page layout for exporting. We need to set the map frame extent and replace all replaceable text.
                 */

                // Create a COM envelope
                IEnvelope env = new EnvelopeClass();
                env.XMin = exportParameters.Extent.XMin;
                env.XMax = exportParameters.Extent.XMax;
                env.YMin = exportParameters.Extent.YMin;
                env.YMax = exportParameters.Extent.YMax;

                // Set the map frame to the exported extent and refresh it. This will force the scalebar to update.
                ((IActiveView)templateMap).Extent = env;
                ((IActiveView)templateMap).Refresh();
                ((IActiveView)pageLayout).Refresh();
                templateLayer = null;
                templateMap = null;

                // replace all text elements before exporting layout
                graphicsContainer.Reset();
                element = graphicsContainer.Next();
                while (element != null)
                {
                    IEnvelope envelope = element.Geometry.Envelope ;
                    float x = Convert.ToSingle(envelope.XMin * 72) + doc.RightMargin;
                    float y = Convert.ToSingle(envelope.YMin * 72) + doc.TopMargin;

                    //Console.Write("\nElement Found: ");					

                    if (element is ITextElement)
                    {
                        _log.AddMessage(3, 8000, "Text");
                        ITextElement textElement = element as ITextElement;

                        // replace the text element if needed
                        if (textElement.Text.StartsWith("{") && textElement.Text.EndsWith("}"))
                        {
                            var key = textElement.Text.Substring(1, textElement.Text.Length - 2);
                            if (exportParameters.TextElements.ContainsKey(key))
                            {
                                textElement.Text = exportParameters.TextElements[key];
                            }

                        }
                    }

                    element = graphicsContainer.Next();
                }

                // Export the layout
                string imageFile = ExportLayout(pageLayout);
                PdfReader layoutReader = new PdfReader(imageFile); 
                PdfImportedPage layoutPage = writer.GetImportedPage(layoutReader, 1); // import page 1, the only page	
                writer.DirectContent.AddTemplate(layoutPage, 0, 0); 

                // Add legend and attributes
                graphicsContainer.Reset();
                element = graphicsContainer.Next();
                while (element != null)
                {
                    IEnvelope envelope = element.Geometry.Envelope; ;
                    float x = Convert.ToSingle(envelope.XMin * 72) + doc.RightMargin;
                    float y = Convert.ToSingle(envelope.YMin * 72) + doc.TopMargin;
                    
                    if (element is ESRI.ArcGIS.Carto.IRectangleElement)
                    {
                        IElementProperties elementProperties = element as IElementProperties;

                        float rectangleWidth = Convert.ToSingle(envelope.Width * 72);
                        float rectangleHeight = Convert.ToSingle(envelope.Height * 72);
                        float topY = Convert.ToSingle(envelope.YMax * 72) + doc.TopMargin;

                        if (elementProperties.Name.ToLower() == "{legend}")
                        {
                            //HACK: Add some padding incase the rectangle has a border. We should check the element for a border or not.                            
                            rectangleWidth -= 8;
                            x += 4;

                            rectangleHeight -= 8;
                            y += 4;
                            topY -= 4;
                        }                        

                        // Set clipping on the PDF area so the text does not overflow into other areas
                        writer.DirectContent.SaveState();
                        writer.DirectContent.Rectangle(x, y, rectangleWidth, rectangleHeight);
                        writer.DirectContent.Clip();
                        writer.DirectContent.NewPath();

                        if (elementProperties.Name.ToLower() == "{attributes}")
                        {
                            float tableWidth = Convert.ToSingle(envelope.Width * 72);

                            // add attribute data
                            PdfPTable table = new PdfPTable(2);
                            table.TotalWidth = tableWidth;
                            table.LockedWidth = true;

                            float colWidth = tableWidth / 2;
                            float[] widths = new float[] { colWidth, colWidth };
                            table.SetWidths(widths);

                            foreach (KeyValuePair<string, object> kvp in exportParameters.Attributes)
                            {
                                PdfPCell cell1 = new PdfPCell(new Phrase(kvp.Key, new iTextSharp.text.Font(iTextSharp.text.Font.FontFamily.HELVETICA, 10f, iTextSharp.text.Font.NORMAL, iTextSharp.text.BaseColor.BLACK)));
                                cell1.Border = 0;
                                table.AddCell(cell1);

                                PdfPCell cell2 = new PdfPCell(new Phrase(kvp.Value.ToString(), new iTextSharp.text.Font(iTextSharp.text.Font.FontFamily.HELVETICA, 10f, iTextSharp.text.Font.NORMAL, iTextSharp.text.BaseColor.BLACK)));
                                cell2.Border = 0;
                                table.AddCell(cell2);
                            }
                            table.WriteSelectedRows(0, -1, x, topY, writer.DirectContent);
                        }
                        else if (elementProperties.Name.ToLower() == "{legend}")
                        {
                            PdfPTable legendTable = CreateLegend(exportParameters.Resources, rectangleWidth);
                            legendTable.WriteSelectedRows(0, -1, x, topY, writer.DirectContent);
                        }

                        writer.DirectContent.RestoreState();
                    }

                    element = graphicsContainer.Next();
                }
                doc.Close();
                mapReader.Close();
            }
            finally
            {
                if (mapReader != null)
                {
                    mapReader.Close();
                }
            }

            return fileName;
        }


        #region [ Legend ]
        /// <summary>
        /// Creates the legend.
        /// </summary>
        /// <param name="resources">The resources.</param>
        /// <param name="width">The width.</param>
        /// <returns></returns>
        /// <remarks></remarks>
        private PdfPTable CreateLegend(List<ExportMapResource> resources, float width)
		{
			PdfPTable table = new PdfPTable(2);
			table.TotalWidth = width;
			table.LockedWidth = true;

            //HACK: hard code the swatch column to 32.
            float[] widths = new float[] { 32, width - 32f };
			table.SetWidths(widths);

            foreach (ExportMapResource exportResource in resources)
			{
				if (exportResource.DisplayInTableOfContents)
				{
					if (exportResource.Resource is AgsMapResource)
					{
						AgsMapResource ags = (AgsMapResource)exportResource.Resource;
						foreach (ESRI.ArcGIS.ADF.ArcGISServer.LayerDescription ld in ags.MapDescription.LayerDescriptions)
						{
							if (ld.Visible)
							{
                                bool displayInTOC = true;

                                if(exportResource.DynamicLayers != null)
                                {
                                    if(exportResource.DynamicLayers.Exists(o => o.ID == ld.LayerID))
                                    {
                                        displayInTOC = exportResource.DynamicLayers.First(o => o.ID == ld.LayerID).DisplayInTOC;
                                    }
                                }

                                if (displayInTOC)
                                {
                                    for (int i = 0; i < ags.MapServerLegendInfos.Length; i++)
                                    {
                                        if (ags.MapServerLegendInfos[i].LayerID == ld.LayerID)
                                        {
                                            CreateTocEntry(table, ref width, ags.MapServerLegendInfos[i]);
                                            break;
                                        }
                                    }
                                }
							}
						}
					}
				}
			}
			return table;
		}

        /// <summary>
        /// Creates the toc entry.
        /// </summary>
        /// <param name="tocTable">The toc table.</param>
        /// <param name="legendWidth">Width of the legend.</param>
        /// <param name="legendInfo">The legend info.</param>
        /// <returns></returns>
        /// <remarks></remarks>
		private static bool CreateTocEntry(PdfPTable tocTable, ref float legendWidth, ESRI.ArcGIS.ADF.ArcGISServer.MapServerLegendInfo legendInfo)
		{
			ESRI.ArcGIS.ADF.ArcGISServer.MapServerLegendClass legendClass = null;

			for (int x = 0; x < legendInfo.LegendGroups.Length; x++)
			{
				ESRI.ArcGIS.ADF.ArcGISServer.MapServerLegendGroup lg = legendInfo.LegendGroups[x];

				if (lg.LegendClasses.Length > 1)
				{
                    // add an empty spacer cell
					PdfPCell cell1 = new PdfPCell(new Phrase("", new iTextSharp.text.Font(iTextSharp.text.Font.FontFamily.HELVETICA, 8f, iTextSharp.text.Font.BOLD, iTextSharp.text.BaseColor.BLACK)));
					cell1.Border = 0;
					tocTable.AddCell(cell1);

                    // add the label
					string cell2Text = legendInfo.Name;
					PdfPCell cell2 = new PdfPCell(new Phrase(cell2Text, new iTextSharp.text.Font(iTextSharp.text.Font.FontFamily.HELVETICA, 8f, iTextSharp.text.Font.BOLD, iTextSharp.text.BaseColor.BLACK)));
					cell2.Border = 0;
					tocTable.AddCell(cell2);
				}

				for (int i = 0; i < lg.LegendClasses.Length; i++)
				{
                    // get the image
					legendClass = lg.LegendClasses[i];
					iTextSharp.text.Image img = iTextSharp.text.Image.GetInstance(legendClass.SymbolImage.ImageData);
					float scalePercent = 72f / Convert.ToSingle(legendClass.SymbolImage.ImageDPI);
					img.ScaleToFit(Convert.ToSingle(legendClass.SymbolImage.ImageWidth) * scalePercent, Convert.ToSingle(legendClass.SymbolImage.ImageHeight) * scalePercent);
					img.Alignment = iTextSharp.text.Image.MIDDLE_ALIGN;

                    // add the image to a cell
					PdfPCell cell = new PdfPCell(img);
                    cell.VerticalAlignment = iTextSharp.text.Element.ALIGN_MIDDLE;
					cell.Border = 0;
					tocTable.AddCell(cell);

                    // add the label
					string cell2Text = lg.LegendClasses.Length > 1 ? legendClass.Label : legendInfo.Name;
					PdfPCell cell2 = new PdfPCell(new Phrase(cell2Text, new iTextSharp.text.Font(iTextSharp.text.Font.FontFamily.HELVETICA, 8f, iTextSharp.text.Font.NORMAL, iTextSharp.text.BaseColor.BLACK)));
                    cell2.VerticalAlignment = iTextSharp.text.Element.ALIGN_MIDDLE;
					cell2.Border = 0;
					tocTable.AddCell(cell2);
				}
			}
			return true;
        }
        #endregion

        #region [ Markup ]
        /// <summary>
        /// Draws the markup.
        /// </summary>
        /// <param name="writer">The writer.</param>
        /// <param name="x">The x.</param>
        /// <param name="y">The y.</param>
        /// <param name="exportParameters">The export parameters.</param>
        /// <remarks></remarks>
        private void DrawMarkup(PdfWriter writer, float x, float y, ExportParameters exportParameters)
        {
            int pageWidth = Convert.ToInt32(_mapWidthInInches * 72);
            int pageHeight = Convert.ToInt32(_mapHeightInInches * 72);


            // Export each graphic to pdf file, then add it as a layer. Exporting to a temp file allows the graphic
            // to be cropped automatically without having to do any complex math
            PdfLayer layer = new PdfLayer("Graphics", writer);

            PdfContentByte cb = writer.DirectContent;

            foreach (WorldViewSolutions.Graphics.Graphic graphic in exportParameters.Graphics)
            { 
                try
                {
                    cb.SaveState();

                    // set the fill/stroke opacity
                    PdfGState gs1 = new PdfGState();
                    gs1.FillOpacity = (float)graphic.Style.FillOpacity;
                    gs1.StrokeOpacity = (float)graphic.Style.LineOpacity;                 
                    cb.SetGState(gs1);

                    // set clipping so markup does not overflow outside of the map
                    cb.Rectangle(x, y, pageWidth, pageHeight);
                    cb.Clip();
                    cb.NewPath();

                    // create a layer so the graphic's visibility can be toggled on/off
                    PdfLayer layer2 = new PdfLayer(graphic.Name, writer);
                    layer.AddChild(layer2);
                    writer.DirectContent.BeginLayer(layer2);

                    // set the fill color
                    System.Drawing.Color rgb = System.Drawing.ColorTranslator.FromHtml(graphic.Style.FillColor);
                    cb.SetRGBColorFill(rgb.R, rgb.G, rgb.B);

                    // set the stroke/line color.
                    System.Drawing.Color rgb2 = System.Drawing.ColorTranslator.FromHtml(graphic.Style.LineColor);
                    cb.SetRGBColorStroke(rgb2.R, rgb2.G, rgb2.B);

                    // set the line width
                    float lineWidth = Convert.ToSingle(graphic.Style.LineWidth);
                    cb.SetLineWidth(lineWidth);

                    // set the line style (solid, dash, etc..)
                    switch (graphic.Style.LineType)
                    {
                        case "longdash":                            
                            cb.SetLineDash(8f, 2f, 0);
                            break;
                        case "shortdash":
                            cb.SetLineDash(4f, 2f, 0);
                            break;
                        case "dot":
                            //cb.SetLineCap(PdfContentByte.LINE_CAP_ROUND);
                            cb.SetLineDash(lineWidth, lineWidth, 0);
                            break;
                        case "dashdot":
                            break;
                    }

                    switch (graphic.Geometry.GetType().Name)
                    {
                        case "Point":                            
                            WorldViewSolutions.Geometry.Point point = (WorldViewSolutions.Geometry.Point)graphic.Geometry;
                            System.Drawing.Point urlPoint = SpatialUtilities.ToScreenUnitInverse(exportParameters.Extent, pageWidth, pageHeight, point.X, point.Y);
                            if (graphic.Style.IconStyle != null)
                            {
                                if (graphic.Style.IconStyle.Url != null)
                                {
                                    iTextSharp.text.Image img = iTextSharp.text.Image.GetInstance(new Uri(graphic.Style.IconStyle.Url));
                                    
                                    img.SetAbsolutePosition(urlPoint.X + x + graphic.Style.IconStyle.OffsetX, urlPoint.Y + y + graphic.Style.IconStyle.OffsetY);
                                    //img.ScaleToFit(Convert.ToSingle(envelope.Width * 72), Convert.ToSingle(envelope.Height * 72));
                                    writer.DirectContent.AddImage(img);
                                }
                            }
                            else
                            {
                                // we tell the ContentByte we're ready to draw text 
                                writer.DirectContent.BeginText();

                                BaseFont bfTimes = BaseFont.CreateFont(BaseFont.HELVETICA, BaseFont.CP1252, false);
                                writer.DirectContent.SetFontAndSize(bfTimes, Convert.ToSingle(graphic.Style.TextSize));
                                
                                //TODO: Change Color

                                // we draw some text on a certain position 
                                writer.DirectContent.SetTextMatrix(urlPoint.X + x, urlPoint.Y + y);
                                writer.DirectContent.ShowText(graphic.Name);

                                // we tell the contentByte, we've finished drawing text 
                                writer.DirectContent.EndText(); 
                            }
                            
                            break;
                        case "Polyline":
                            WorldViewSolutions.Geometry.Polyline polyline = (WorldViewSolutions.Geometry.Polyline)graphic.Geometry;
                            foreach (WorldViewSolutions.Geometry.Path mdPath in polyline.Paths)
                            {
                                System.Drawing.Point startPoint = SpatialUtilities.ToScreenUnitInverse(exportParameters.Extent, pageWidth, pageHeight, mdPath.Points[0].X, mdPath.Points[0].Y);
                                cb.MoveTo(startPoint.X + x, startPoint.Y + y);

                                for (int i = 1; i < mdPath.Points.Count; i++)
                                {
                                    startPoint = SpatialUtilities.ToScreenUnitInverse(exportParameters.Extent, pageWidth, pageHeight, mdPath.Points[i].X, mdPath.Points[i].Y);
                                    cb.LineTo(startPoint.X + x, startPoint.Y + y);
                                }
                                cb.Stroke();
                            }
                            break;
                        case "Polygon":

                            WorldViewSolutions.Geometry.Polygon polygon = (WorldViewSolutions.Geometry.Polygon)graphic.Geometry;

                            foreach (WorldViewSolutions.Geometry.Ring ring in polygon.Rings)
                            {
                                System.Drawing.Point startPoint = SpatialUtilities.ToScreenUnitInverse(exportParameters.Extent, pageWidth, pageHeight, ring.Points[0].X, ring.Points[0].Y);
                                cb.MoveTo(startPoint.X + x, startPoint.Y + y);

                                for (int i = 1; i < ring.Points.Count; i++)
                                {
                                    startPoint = SpatialUtilities.ToScreenUnitInverse(exportParameters.Extent, pageWidth, pageHeight, ring.Points[i].X, ring.Points[i].Y);
                                    cb.LineTo(startPoint.X + x, startPoint.Y + y);
                                }
                            }

                            cb.EoFillStroke();
                            break;
                    }
                    writer.DirectContent.EndLayer();
                }
                finally
                {

                    cb.RestoreState();
                    
                }
            }
        }
        #endregion

        /// <summary>
        /// Exports the layout.
        /// </summary>
        /// <param name="pageLayout">The page layout.</param>
        /// <returns></returns>
        /// <remarks></remarks>
        private string ExportLayout(IPageLayout pageLayout)
        {
            IActiveView activeView = (IActiveView)pageLayout;
            IExport exporter = new ExportPDFClass();// ExportEMFClass();
            IEnvelope env1 = new EnvelopeClass();
            
            string fileName = System.IO.Path.Combine(OutputDir, "_ags_" + Guid.NewGuid().ToString().Replace("-", "") + ".pdf");
            // save the original page size
            double pageWidth;
            double pageHeight;
            pageLayout.Page.QuerySize(out pageWidth, out pageHeight);

           
            env1.UpperLeft = pageLayout.Page.PrintableBounds.UpperLeft;
            env1.UpperRight = pageLayout.Page.PrintableBounds.UpperRight;
            env1.LowerLeft = pageLayout.Page.PrintableBounds.LowerLeft;
            env1.LowerRight = pageLayout.Page.PrintableBounds.LowerRight;
            
            double dpi = exporter.Resolution; //default screen resolution is usually 96

            tagRECT p_tagRECT = new tagRECT();
            p_tagRECT.bottom = Convert.ToInt32(env1.YMax * dpi); // convert from inches to pixels
            p_tagRECT.top = Convert.ToInt32(env1.YMin * dpi);
            p_tagRECT.left = Convert.ToInt32(env1.XMin * dpi);
            p_tagRECT.right = Convert.ToInt32(env1.XMax * dpi);

            env1.PutCoords(p_tagRECT.left, p_tagRECT.top, p_tagRECT.right, p_tagRECT.bottom);

            exporter.PixelBounds = env1;
            exporter.ExportFileName = fileName;
            exporter.Resolution = dpi;

            // Do the export
            int hdc = exporter.StartExporting();
            activeView.Output(hdc, Convert.ToInt32(dpi), ref p_tagRECT, null, null);
            exporter.FinishExporting();
            exporter.Cleanup();

            return fileName;
        }

        /// <summary>
        /// Exports the surround.
        /// </summary>
        /// <param name="pageLayout">The page layout.</param>
        /// <param name="element">The element.</param>
        /// <returns></returns>
        /// <remarks></remarks>
        private string ExportSurround(IPageLayout pageLayout, ESRI.ArcGIS.Carto.IElement element)
		{
			IActiveView activeView = (IActiveView)pageLayout;
			IExport exporter = new ExportPDFClass();// ExportEMFClass();
			IEnvelope env1 = new EnvelopeClass();
			IEnvelope env2 = new EnvelopeClass();
			IEnvelope env3 = new EnvelopeClass();
			string fileName = System.IO.Path.Combine(OutputDir, "_ags_" + Guid.NewGuid().ToString().Replace("-", "") + ".pdf");
			// save the original page size
			double pageWidth;
			double pageHeight;
			pageLayout.Page.QuerySize(out pageWidth, out pageHeight);
                       
			env1 = element.Geometry.Envelope;
			// Shrink the page to the bounds of the element
            if (element is ITextElement)
            {
                env2.UpperLeft = pageLayout.Page.PrintableBounds.UpperLeft;
                env2.UpperRight = pageLayout.Page.PrintableBounds.UpperRight;
                env2.LowerLeft = pageLayout.Page.PrintableBounds.LowerLeft;
                env2.LowerRight = pageLayout.Page.PrintableBounds.LowerRight;
            }
            else
            {
                pageLayout.Page.PutCustomSize(env1.Width, env1.Height);

                // move the element to the printable area of the page
                env2.UpperLeft = pageLayout.Page.PrintableBounds.UpperLeft;
                env2.UpperRight = pageLayout.Page.PrintableBounds.UpperRight;
                env2.LowerLeft = pageLayout.Page.PrintableBounds.LowerLeft;
                env2.LowerRight = pageLayout.Page.PrintableBounds.LowerRight;
                element.Geometry = env2;
            }

            double dpi = exporter.Resolution; //default screen resolution is usually 96

			tagRECT p_tagRECT = new tagRECT();
			p_tagRECT.bottom = Convert.ToInt32(env2.YMax * dpi); // convert from inches to pixels
			p_tagRECT.top = Convert.ToInt32(env2.YMin * dpi);
			p_tagRECT.left = Convert.ToInt32(env2.XMin * dpi);
			p_tagRECT.right = Convert.ToInt32(env2.XMax * dpi);

			env3.PutCoords(p_tagRECT.left, p_tagRECT.top, p_tagRECT.right, p_tagRECT.bottom);

			exporter.PixelBounds = env3;
			exporter.ExportFileName = fileName;
			exporter.Resolution = dpi;
						
			// Do the export
			int hdc = exporter.StartExporting();
			activeView.Output(hdc, Convert.ToInt32(dpi), ref p_tagRECT, null, null);
			exporter.FinishExporting();
			exporter.Cleanup();

			// set the all objects back to their original positions
			pageLayout.Page.PutCustomSize(pageWidth, pageHeight);
			env1.XMin = pageLayout.Page.PrintableBounds.XMax;
			env1.XMax = env2.XMin + env1.XMax;
			element.Geometry = env1;

			
			return fileName;			
		}

        /// <summary>
        /// Gets the name of the logo element by.
        /// </summary>
        /// <param name="sElementName">Name of the s element.</param>
        /// <param name="sFileName">Name of the s file.</param>
        /// <param name="layout">The layout.</param>
        /// <returns></returns>
        /// <remarks></remarks>
		private bool getLogoElementByName(string sElementName, string sFileName, IGraphicsContainer layout)
		{
			bool bResult = false;
			layout.Reset();
			ESRI.ArcGIS.Carto.IElement element = layout.Next();
			while (element != null)
			{
				if (element is IPictureElement)
				{
					if (((IElementProperties)element).Name == sElementName)
					{

						((IPictureElement)element).ImportPictureFromFile(sFileName);
						bResult = !bResult;
						break;
					}

				}
				element = layout.Next();
			}
			return bResult;

		}

        /// <summary>
        /// Gets the name of the text element by.
        /// </summary>
        /// <param name="queryText">The query text.</param>
        /// <param name="layout">The layout.</param>
        /// <returns></returns>
		private ITextElement getTextElementByName(string queryText, IGraphicsContainer layout)
		{
			queryText = queryText.ToLower();

			if (!queryText.StartsWith("%"))
				queryText = "%" + queryText + "%";
			layout.Reset();
			ESRI.ArcGIS.Carto.IElement element = layout.Next();
			while (element != null)
			{
				if (element is ITextElement)
				{
					if (((ITextElement)element).Text.ToLower() == queryText)
						return element as ITextElement;
				}
				element = layout.Next();
			}
			return null;
		}

        /// <summary>
        /// Strings to enum.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="name">The name.</param>
        /// <returns></returns>
		public T StringToEnum<T>(string name)
		{
			return (T)Enum.Parse(typeof(T), name);
		}

        /// <summary>
        /// Returns the image codec with the given mime type
        /// </summary>
        /// <param name="mimeType">Type of the MIME.</param>
        /// <returns></returns>
        private static ImageCodecInfo GetEncoderInfo(string mimeType)
        {
            // Get image codecs for all image formats 
            ImageCodecInfo[] codecs = ImageCodecInfo.GetImageEncoders();

            // Find the correct image codec 
            for (int i = 0; i < codecs.Length; i++)
                if (codecs[i].MimeType == mimeType)
                    return codecs[i];
            return null;
        }
	}
}